Skip to content

Fix psi systemd install inside a container (daemon-reload + quadlet Type + ContainerName)#17

Merged
jdoss merged 2 commits intomasterfrom
fix/installer-daemon-reload
Apr 8, 2026
Merged

Fix psi systemd install inside a container (daemon-reload + quadlet Type + ContainerName)#17
jdoss merged 2 commits intomasterfrom
fix/installer-daemon-reload

Conversation

@jdoss
Copy link
Copy Markdown
Contributor

@jdoss jdoss commented Apr 8, 2026

Summary

Three bugs that together prevent psi systemd install --mode container from producing a working install when run inside a psi container:

  1. daemon-reload crash. Installer's _daemon_reload went straight to subprocess.run(["systemctl", "daemon-reload"], check=True), which fails inside a container with System has not been booted with systemd as init system (PID 1) and aborts the whole install with a stacktrace after the quadlet files have already been written.
  2. Invalid Type=simple. generate_container_serve_quadlet emitted Type=simple in the [Service] section. Podman's quadlet generator rejects this: converting "psi-secrets.container": invalid service Type 'simple'. The resulting .service unit is never created, so systemctl restart psi-secrets.service fails with Unit not found.
  3. Missing ContainerName. Without ContainerName=, quadlet names the running container systemd-psi-secrets (etc.). Operator commands like sudo podman exec psi-secrets psi cache status silently stop working against the regenerated unit.

Why

This was discovered while testing PR #16 on the test server. The operator ran the one-shot container invocation to regenerate quadlets, hit the daemon-reload crash, patched around it manually, then hit Unit psi-secrets.service not found on restart because the emitted quadlet was untranslatable. podman exec psi-secrets would also break even if the quadlet did translate, because the container name would have changed.

The symptoms only appeared now because until PR #16 / #17 nobody had actually run psi systemd install --mode container in a real deployment — the existing test server uses a Butane-managed hand-crafted quadlet.

What changes

psi/systemd.py

  • New public daemon_reload(scope) helper that tries D-Bus first, falls back to systemctl, and downgrades both FileNotFoundError and CalledProcessError to warnings.
  • _dbus_daemon_reload(scope) as the underlying D-Bus call.

psi/installer.py

  • _daemon_reload now delegates to psi.systemd.daemon_reload.

psi/setup.py

  • Removes the local duplicate helpers (_systemd_daemon_reload, _dbus_daemon_reload), imports daemon_reload from psi.systemd, drops the now-unused subprocess import.

psi/unitgen.py

  • generate_container_serve_quadlet:
    • Drops Type=simple (quadlet default Type=notify works via podman's sdnotify wiring).
    • Adds ContainerName=psi-secrets.
  • generate_container_provider_setup_quadlet: adds ContainerName=psi-<provider>-setup.
  • generate_container_tls_renew_quadlet: adds ContainerName=psi-tls-renew.

Tests

  • tests/test_systemd.py: moves the 5 daemon-reload tests from test_setup.py and adds a regression test covering the CalledProcessError path that triggered the container crash.
  • tests/test_setup.py: deletes the moved tests and the stale import.
  • tests/test_unitgen.py: new TestQuadletTranslatability class with 5 tests — no Type=simple under any cache backend, Type=oneshot preserved on setup, ContainerName present on all three container quadlets.

Test plan

  • uv run ruff check psi/ tests/ — clean
  • uv run ruff format --check psi/ tests/ — clean
  • uv run ty check — clean
  • uv run pytest -q — 294 passed (6 new)
  • On the test server: re-run the one-shot podman run ... systemd install ... and confirm it exits 0 with only a warning about systemctl
  • sudo systemctl daemon-reload on the host, then systemctl restart psi-secrets.service — unit must translate cleanly and the container must come up healthy
  • sudo podman exec -i psi-secrets psi cache status — returns in well under a second
  • sudo podman exec -i psi-secrets psi cache status --verify — flag now recognized; decrypts via HSM

jdoss added 2 commits April 8, 2026 00:15
Installer's _daemon_reload went straight to subprocess systemctl, which
blows up inside a container with 'System has not been booted with
systemd as init system (PID 1)'. The quadlet files had already been
written to disk before this point, leaving the operator with a
half-applied install and a bug-report traceback.

setup.py already had the right pattern — try D-Bus first, fall back to
systemctl, warn instead of raise on failure — but installer.py never
got updated to use it. Move the helper into psi.systemd as a public
daemon_reload() and have both setup.py and installer.py call it.
Downgrade CalledProcessError from systemctl to a warning so the
install keeps going; the operator can run daemon-reload on the host
afterward.

The regression test patches subprocess.run to raise CalledProcessError
and confirms daemon_reload() returns normally, matching the behavior
needed when running inside the psi container.
psi systemd install wrote a quadlet that podman quadlet-generator
rejected with:

  converting "psi-secrets.container": invalid service Type 'simple'

The .service unit was never created, so systemctl restart
psi-secrets.service failed with 'Unit not found' — a half-applied
install with no clear error. Quadlet .container units only allow
Type=notify (default), forking, oneshot, or exec. Long-running psi
serve works fine under the Type=notify default, which podman wires up
via its built-in sdnotify support.

Also add ContainerName= to the serve, setup, and tls-renew generators.
Without it, quadlet names the container systemd-<unit>, and routine
operator commands like podman exec psi-secrets break.
@jdoss jdoss changed the title Fix psi systemd install crashing inside a container Fix psi systemd install inside a container (daemon-reload + quadlet Type + ContainerName) Apr 8, 2026
@jdoss jdoss merged commit cbb2890 into master Apr 8, 2026
2 checks passed
@jdoss jdoss deleted the fix/installer-daemon-reload branch April 8, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant